//-----------------------------------------------------------------------------
// Filename: CreateUserController.cs
//
// Description: A controller to create a new user account for the SIP Switch.
//
// History:
// 02 Nov 2006	Aaron Clauson	Created.
//
// License: 
// Aaron Clauson.
//-----------------------------------------------------------------------------

using System;
using System.Collections;
using System.Configuration;
using System.Net;
using System.Text.RegularExpressions;
using System.Web;
using System.Xml;
using MaverickLite.Security;
using BlueFace.Sys;
using BlueFace.VoIP.App.SIP;
using BlueFace.VoIP.SIPServerCores.StatefulProxy;
using log4net;

namespace SIPSwitch.Web
{
	public class CreateUserController : SecureXmlController
	{
        public const int PASSWORD_MIN_LENGTH = 6;
        public const int PASSWORD_MAX_LENGTH = 15;
        public const int USERNAME_MIN_LENGTH = 5;
        public const string DEFAULT_SIP_DOMAIN = "sip.mysipswitch.com";

        // Only non-printable non-alphanumeric ASCII characters missing are ; \ and space. The semi-colon isn't accepted by 
        // Netgears and the space has the potential to creat too much confusion wiht the users and \ with the system.
        public static readonly char[] NONAPLPHANUM_ALLOWED_PASSWORD_CHARS = new char[]{'!','"','$','%','&','(',')','*',
										   '+',',','.','/',':','<','=','>','?','@','[',']','^','_','`','{','|','}','~'};
        public static readonly string USERNAME_ALLOWED_CHARS = @"a-zA-Z0-9_\-";

        public const string EXAMPLE_EXTENSION_DIALPLAN =
            "#Ruby\n" +
            "# Welcome to mysipswitch, this is your default Ruby dialplan.\n" +
            "# The default rules for incoming calls to yourusername@sip.mysipswitch.com and for your\n" +
            "# outgoing calls to different providers have been configured for you. To use the outgoing rules you need\n" +
            "# add SIP Providers with names of providerx where x is 1 to 3. You can then use a prefix of *x for\n" +
            "# calls to providerx. For example to place a call to 0012345678 at provider1 you would dial *10012345678.\n" +
            "\n" +
            "sys.Log(\"call from #{req.Header.From.FromURI.ToString()} to #{req.URI.User}.\")\n" +
            "\n" +
            "if sys.In then\n" +
            "  # Do your incoming call processing customisations here.\n" +
            "   if sys.IsAvailable() then\n" + 
            "       sys.Dial(\"#{sys.Username}@local\")                  # Forwards all incoming calls to your registered contacts.\n" +
            "   else\n" +
            "       sys.Log(\"#{sys.Username} is not online.\")\n" +
            "       sys.Respond(480, \"#{sys.Username} Not online\")\n" +
            "   end\n" +
            "else\n" +
            "  # Do your outgoing call processing customisations here.\n" +
            "  case req.URI.User\n" +
            "    when /^303$/ then sys.Dial(\"303@sip.blueface.ie\")  # Blueface speaking clock.\n" +
            "    when /^612$/ then sys.Dial(\"612@fwd.pulver.com\")   # FWD speaking clock.\n" +
            "    when /^\\\\*1/ then sys.Dial(\"${dst:2}@provider1\")   # Forward outgoing calls with a *1 prefix to provider1.\n" +
            "    when /^\\\\*2/ then sys.Dial(\"${dst:2}@provider2\")   # Forward outgoing calls with a *2 prefix to provider2.\n" +
            "    when /^\\\\*3/ then sys.Dial(\"${dst:2}@provider3\")   # Forward outgoing calls with a *3 prefix to provider3.\n" +
            "    else sys.Dial(\"provider1\")\n" +
            "  end\n" +
            "end\n";

		private new static ILog logger = AppState.GetLogger("sipswitch");

		private static string m_switchDBConnStr = ConfigurationManager.AppSettings["SIPSwitchDBConnStr"];
        private static StorageTypes m_switchStorageType = StorageTypesConverter.GetStorageType(ConfigurationManager.AppSettings["SIPSwitchDBType"]);
		private StorageLayer m_sipSwitchStorageLayer = null;
        private SIPAssetPersistor<SIPAccount> m_sipAccountPersistor;

		public CreateUserController(XmlElement initParams) : base(initParams)
		{
            m_sipSwitchStorageLayer = new StorageLayer(m_switchStorageType, m_switchDBConnStr);
            m_sipAccountPersistor = SIPAssetPersistorFactory.CreateSIPAccountPersistor(m_switchStorageType, m_switchDBConnStr);
		}

		protected override void InitialiseRequest(HttpContext context)
		{
            try
            {
                base.InitialiseRequest(context);

                // Logout any existing sessions.
                if (this.m_authenticated)
                {
                    base.Logout(base.m_authenticator, MavRequestState, MavSessionState);
                }

                Hashtable formData = MavRequestState.GetRequestData();
                string create = formData["create"] as string;

                if (create == null || create.Trim().Length == 0)
                {
                    // This is the initial visit to the page no form data has been submitted.
                    return;
                }

                string username = formData["switchusername"] as string;
                string switchPassword = formData["switchpassword"] as string;
                string switchEmailAddress = formData["switchemailaddress"] as string;
                string terms = formData["terms"] as string;

                string formDataNode = "<page><formdata>" +
                        "<switchusername>" + username + "</switchusername>" +
                        "<switchpassword>" + switchPassword + "</switchpassword>" +
                        "<switchemailaddress>" + switchEmailAddress + "</switchemailaddress>" +
                        "<terms>" + terms + "</terms>" +
                        "</formdata></page>";

                logger.Debug("Attempting to create new configuration for " + username + ".");

                bool isDataClean = true;

                if (username == null || username.Trim().Length < USERNAME_MIN_LENGTH)
                {
                    this.AddErrorMessage("The username provided was empty or too short. It must be at least " + USERNAME_MIN_LENGTH + " characters long.");
                    isDataClean = false;
                }
                else if (Regex.Match(username, "[^" + USERNAME_ALLOWED_CHARS + "]").Success)
                {
                    this.AddErrorMessage("The username had an invalid character, characters permitted are alpha-numeric and .-_.");
                    isDataClean = false;
                }
                else if (
                    Convert.ToInt32(m_sipSwitchStorageLayer.ExecuteScalar("select count(*) from customers where upper(username) = '" + username.Replace("'", "''").ToUpper() + "'")) > 0 ||
                    Convert.ToInt32(m_sipSwitchStorageLayer.ExecuteScalar("select count(*) from extensions where upper(extension) = '" + username.Replace("'", "''").ToUpper() + "' and domainid = (select domainid from domains where domain = '" + DEFAULT_SIP_DOMAIN + "')")) > 0 ||
                    Convert.ToInt32(m_sipSwitchStorageLayer.ExecuteScalar("select count(*) from sipaccounts where upper(sipusername) = '" + username.Replace("'", "''").ToUpper() + "' and domainid = (select domainid from domains where domain = '" + DEFAULT_SIP_DOMAIN + "')")) > 0)
                {
                    this.AddErrorMessage(username + " is already taken, please choose a different one.");
                    isDataClean = false;
                }
                else if (switchPassword == null || switchPassword.Trim().Length == 0)
                {
                    this.AddErrorMessage("A password must be specified.");
                    isDataClean = false;
                }
                else if (switchPassword.Length < PASSWORD_MIN_LENGTH || switchPassword.Length > PASSWORD_MAX_LENGTH)
                {
                    this.AddErrorMessage("The Password field must be at least " + PASSWORD_MIN_LENGTH + " characters and no more than " + PASSWORD_MAX_LENGTH + " characters.");
                    isDataClean = false;
                }
                else if (switchEmailAddress == null || switchEmailAddress.Trim().Length == 0)
                {
                    this.AddErrorMessage("An email address must be provided. It will be used for forgotten passwords so please use a valid one.");
                    isDataClean = false;
                }
                else if (!Regex.Match(switchEmailAddress, @"\S+@\S+\.\S+").Success)
                {
                    this.AddErrorMessage("Please specify a valid email address.");
                    isDataClean = false;
                }
                else if (terms != "on")
                {
                    this.AddErrorMessage("You must agree to the Terms and Conditions if you would like a SIP Switch account enabled.");
                    isDataClean = false;
                }
                else
                {
                    #region Check the password illegal characters.

                    char[] passwordChars = switchPassword.ToCharArray();

                    bool illegalCharFound = false;
                    char illegalChar = ' ';

                    foreach (char passwordChar in passwordChars)
                    {
                        if (Regex.Match(passwordChar.ToString(), "[a-zA-Z0-9]").Success)
                        {
                            continue;
                        }
                        else
                        {
                            bool validChar = false;
                            foreach (char allowedChar in NONAPLPHANUM_ALLOWED_PASSWORD_CHARS)
                            {
                                if (allowedChar == passwordChar)
                                {
                                    validChar = true;
                                    break;
                                }
                            }

                            if (validChar)
                            {
                                continue;
                            }
                            else
                            {
                                illegalCharFound = true;
                                illegalChar = passwordChar;
                                break;
                            }
                        }
                    }

                    #endregion

                    if (illegalCharFound)
                    {
                        this.AddErrorMessage("Your password has an invalid character " + illegalChar + " it can only contain a to Z, 0 to 9 and characters in this set " + SafeXML.MakeSafeXML(new String(NONAPLPHANUM_ALLOWED_PASSWORD_CHARS)) + ".");
                        isDataClean = false;
                    }
                }

                if (isDataClean)
                {
                    string usernameCleanStr = username.Replace("'", "''");
                    string passwordCleanStr = switchPassword.Replace("'", "''");

                    string insertCustomerSQL =
                       "insert into customers (" +
                       "customerid, " +
                       "lastname, " +
                       "firstname, " +
                       "username," +
                       "password," +
                       "emailaddress) values " +
                       "('" + Guid.NewGuid() + "', " +
                       "''," +
                       "''," +
                       "'" + usernameCleanStr  + "', " +
                       "'" + passwordCleanStr + "', " +
                       "'" + switchEmailAddress.Replace("'", "''") + "')";

                    logger.Debug("Insert Customer SQL: " + insertCustomerSQL);

                    m_sipSwitchStorageLayer.ExecuteNonQuery(insertCustomerSQL);

                    // Create new SIP switch account.
                    string insertSQL =
                        "insert into dialplans (" +
                        "dialplanid, " +
                        "username, " +
                        "dialplan, " +
                        "updated, " +
                        "lastupdate, " +
                        "inserted) values " +
                        "('" + Guid.NewGuid() + "', " +
                        "'" + usernameCleanStr  + "', " +
                        "'" + EXAMPLE_EXTENSION_DIALPLAN + "' ," +
                        "'1'," +
                        "default, " +
                        "default)";

                    m_sipSwitchStorageLayer.ExecuteNonQuery(insertSQL);

                    SIPAccount sipAccount = new SIPAccount(usernameCleanStr, usernameCleanStr, passwordCleanStr, DEFAULT_SIP_DOMAIN, null);
                    m_sipAccountPersistor.Update(sipAccount);

                    Credentials requestCredentials = new Credentials(username, switchPassword, null);
                    string sessionId = base.m_authenticator.AuthenticateWebRequest(requestCredentials, MavRequestState.RequestIPAddress);
                    AddIdentityMessage(base.m_authenticator, sessionId);
                    MavSessionState.SetSessionValue(AUTHENTICATION_STATE_KEY, sessionId);

                    this.m_pageModel.AddToModel("<page><sipswitchpassword>" + switchPassword + "</sipswitchpassword></page>", true);

                    this.m_view = "created";
                }
                else
                {
                    this.m_pageModel.AddToModel(formDataNode, true);
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception CreateUserController. " + excp.Message);
                this.AddErrorMessage("Exception: " + excp.Message);
            }
		}
	}
}
